/*
 * imagematrix.cpp
 *
 *  Created on: Feb 27, 2011
 *      Author: Mo
 */

#include "imagematrix.h"
#include <string>
#include <fstream>
#include <assert.h>
#include "bitmap.h"
using namespace std;


ImageMatrix::ImageMatrix()
{
	this->rowNum = MROWS;
	this->colNum = MCOLS;
	this->clusterability = -1.0;
}

ImageMatrix::ImageMatrix(const ImageMatrix& other)
{
	this->rowNum = other.rowNum;
	this->colNum = other.colNum;
	this->clusterability = other.clusterability;
	this->clusters.assign(other.clusters.begin(), other.clusters.end());
	for (int row = 0; row != MROWS; row++)
		for (int col = 0; col != MCOLS; col++)
		{
			this->data[row][col] = other.data[row][col];
			this->globalCluster[row][col] = other.globalCluster[row][col];
		}
}

ImageMatrix::ImageMatrix (const string& fileName, int r, int c)
{
	string fileExtention;
	size_t dotPosition;
	dotPosition = fileName.find_last_of(".");
	fileExtention = fileName.substr(dotPosition+1);

	if (fileExtention == "txt")
	{
		this->rowNum = r;
		this->colNum = c;
		this->clusterability = -1;
		ifstream imFile (fileName.c_str());
		assert (imFile);
		for (int i = 0; i != this->rowNum; i++)
			for (int j = 0; j != this->colNum; j++)
				imFile >> this->data[i][j];
	}
	else if (fileExtention == "bmp")
	{
		const char* cFileNameTemp = fileName.c_str();
		char* cFileName = (char*) cFileNameTemp;
		CBitmap inputImage(cFileName);
		this->colNum = inputImage.GetWidth();
		this->colNum = inputImage.GetHeight();
		this->clusterability = -1;
		for (int row = 0; row != this->rowNum; row++)
			for (int col = 0; col != this->colNum; col++)
			{
				if (inputImage.redInRCPixel(row, col) == 0 &&
						inputImage.greenInRCPixel(row, col) == 0 &&
						inputImage.blueInRCPixel(row, col) == 0 )
					this->data[row][col] = BLACK;
				else
					this->data[row][col] = WHITE;
			}
	}
	else
		assert(true && "Error: only .txt and .bmp are accepted for the input file name.");

}

ImageMatrix::ImageMatrix (const string& fileName)
{
	string fileExtention;
	size_t dotPosition;
	dotPosition = fileName.find_last_of(".");
	fileExtention = fileName.substr(dotPosition+1);

	cout << endl << "Reading from the file " << fileName << endl;
	if (fileExtention == "txt")
	{
		int readValue = 0;
		this->rowNum = MROWS;
		this->colNum = MCOLS;
		this->clusterability = -1;
		ifstream imFile (fileName.c_str());
		assert (imFile);
		for (int i = 0; i != this->rowNum; i++)
			for (int j = 0; j != this->colNum; j++)
			{
				imFile >> readValue;
				if (readValue == 0 || readValue == 65)
					this->data[i][j] = 0;
				else if (readValue == 1 || readValue == 66)
					this->data[i][j] = 1;
				
				//imFile >> this->data[i][j];
			}
	}
	else if (fileExtention == "bmp")
	{
		cout << "(a bitmap file of size ";
		char* cFileName = (char*) fileName.c_str();
		CBitmap inputImage(cFileName);
		this->colNum = inputImage.GetWidth();
		this->rowNum = inputImage.GetHeight();
		cout << colNum << "x" << rowNum << ")";
		this->clusterability = -1;
		for (int row = 0; row != this->rowNum; row++)
			for (int col = 0; col != this->colNum; col++)
			{
				if (inputImage.redInRCPixel(row, col) == 0 &&
						inputImage.greenInRCPixel(row, col) == 0 &&
						inputImage.blueInRCPixel(row, col) == 0 )
					this->data[row][col] = BLACK;
				else
					this->data[row][col] = WHITE;
			}
	}
	else
		assert(true && "Error: only .txt and .bmp are accepted for the input file name.");

}

ImageMatrix::ImageMatrix(int** sourceData, int r, int c)
{
	if (this->rowNum != rowNum || this->colNum != colNum)
		cout << "Warning: An instance of ImageMatrix::data is being loaded with a 2D array of different size. " << endl
			<< "ImageMatrix::ImageMatrix(int** sourceData, int r, int c)" << endl;
	this->rowNum = r;
	this->colNum = c;
	//this->clusterNum = -1;
	this->clusterability = -1.0;
//	if (sourceData)
//	{
//		this->data = new int*[this->rowNum];
//		for (int i = 0; i != rowNum; i++)
//		{
//			this->data[i] = new int[this->colNum];
//			for (int j = 0; j != this->colNum; j++)
//				this->data[i][j] = sourceData[i][j];
//		}
//	}
	for (int row = 0; row != MROWS; row++)
		for (int col = 0; col != MCOLS; col++)
		{
			this->data[row][col] = sourceData[row][col];
			//this->refreshClusters();
		}
}

ImageMatrix::ImageMatrix(int sourceData[MROWS][MCOLS], int r, int c)
{
	if (colNum != c || this->rowNum != rowNum)
		cout << "Warning: An instance of ImageMatrix::data is being loaded with a 2D array of different size." << endl
			<< "ImageMatrix::ImageMatrix(int sourceData[rowNum][colNum], int r, int c)";
	this->rowNum = r;
	this->colNum = c;
	//this->clusterNum = -1;
	this->clusterability = -1.0;
//	if (sourceData)
//	{
//		this->data = new int*[this->rowNum];
//		for (int i = 0; i != rowNum; i++)
//		{
//			this->data[i] = new int[this->colNum];
//			for (int j = 0; j != this->colNum; j++)
//				this->data[i][j] = sourceData[i][j];
//		}
//	}
	for (int row = 0; row != MROWS; row++)
		for (int col = 0; col != MCOLS; col++)
		{
			this->data[row][col] = sourceData[row][col];
			//this->refreshClusters();
		}
}

void ImageMatrix::load(int sourceData[MROWS][MCOLS])
{
	for (int row = 0; row != MROWS; row++)
		for (int col = 0; col != MCOLS; col++)
		{
			this->data[row][col] = sourceData[row][col];
			//this->refreshClusters();
		}
}

void ImageMatrix::load(int** sourceData)
{
	for (int row = 0; row != MROWS; row++)
		for (int col = 0; col != MCOLS; col++)
		{
			this->data[row][col] = sourceData[row][col];
			//this->refreshClusters();
		}
}

void ImageMatrix::saveToBMP(const string& fileName) const
{
	//we open the file first, and then we modify it.
	char* cFileName = (char*) fileName.c_str();
	CBitmap outputImage(cFileName);
	assert (this->getColNum() == (int)outputImage.GetWidth() && this->getRowNum() == (int)outputImage.GetHeight()
		&& "Error: Trying to write an ImageMatrix object to a BMP file of a different size");
	for (int row = 0; row != this->rowNum; row++)
		for (int col = 0; col != this->colNum; col++)
			if (this->data[row][col] == BLACK)
				outputImage.setBlackRC(row, col);
			else if (this->data[row][col] == WHITE)
				outputImage.setWhiteRC(row, col);
	outputImage.Save(cFileName,32U);
}

void ImageMatrix::saveToTXT(string fileName) const
{
	ofstream file(fileName.data());
	for (int row = 0; row != this->rowNum; row++)
	{
		for (int col = 0; col != this->colNum; col++)
			file << data[row][col] << "\t";
		file << endl;
	}
}

void ImageMatrix::setRowNum(int r) {this->rowNum = r;}
void ImageMatrix::setColNum(int c) {this->colNum = c;}
void ImageMatrix::setRowColNum(int r, int c) {this->rowNum = r; this->colNum = c;}
void ImageMatrix::setData_noClusterUpdate (int r, int c, int val) {this->data[r][c] = val;}
//void ImageMatrix::setClusterNum(int n) {this->clusterNum = n;}

int ImageMatrix::getValue(int row, int col) const 
{
	if (row < 0 || col < 0 || row >= this->getRowNum() || col >= this->getColNum())
		return OUTOFBOARD;
	else
		return this->data[row][col];
}

int ImageMatrix::getValueWithRotationalIndex(int centerRow, int centerCol, int nbAddress) const
{
	if (nbAddress == 0) 
		return getValue(centerRow,centerCol);
	int nbRow, nbCol;
	int roundedSqrt = (int) sqrt(nbAddress);
	roundedSqrt = (roundedSqrt%2 == 0)? roundedSqrt-1 : roundedSqrt;
	int r = (roundedSqrt + 1) / 2;
	int cornerIndex = (2*r-1)*(2*r-1); //this is the index of the upper left corner nb in this ring (the outmost layer of the radius).
	int offset = nbAddress - cornerIndex;
	if (offset < 2*r)
	{
		nbRow = centerRow - r;
		nbCol = centerCol - r + offset;
	}
	else if (offset < 4*r)
	{
		nbRow = centerRow - r + (offset - 2*r);
		nbCol = centerCol + r;
	}
	else if (offset < 6*r)
	{
		nbRow = centerRow + r;
		nbCol = centerCol + r - (offset - 4*r);
	}
	else if (offset < 8*r)
	{
		nbRow = centerRow + r - (offset - 6*r);
		nbCol = centerCol - r;
	}
	else
		assert ("Error in input nb address: something went wrong.");
	
	return getValue(nbRow, nbCol);
}

//int ImageMatrix::getClusterNum(void) const {return this->clusterNum;}
int ImageMatrix::getRowNum(void) const {return this->rowNum;}
int ImageMatrix::getColNum(void) const {return this->colNum;}

void ImageMatrix::clean()
{
	for (int row = 0; row != this->rowNum; row++)
		for (int col = 0; col != this->colNum; col++)
			this->data[row][col] = 0;
	this->clusters.clear();
	//this->clusterNum = -1;
	this->clusterability = -1.0;
}

void ImageMatrix::addPixelToGlobalCluster(Pixel p, int clusterIndex)
{
	this->globalCluster[p.getRow()][p.getCol()] = clusterIndex;
}

//void ImageMatrix::getNeighborhood (int row, int col, Neighborhood& neighborhood) const
//{
//	neighborhood.updateFromImageMatrix(this, row, col);
//}

void ImageMatrix::refreshClusters()
{

	int highestClusterIndex = 0; //the clusters start from 1, because 0 means no cluster (white back ground)
	int clusterIndex = 0;
	int indexOfClusterToBeMerged;
	Pixel pixel2BChanged; //the pixel in a cluster that needs to be merged into another cluster
	int row2BChanged, col2BChanged; //the row and col of the above pixel.
	vector<Cluster> tempCluster; //this vector will have empty elements because of merging the clusters. we will clean it in this->clusters later.


	//Checking the first pixel
	if (this->data[0][0] == BLACK)
	{
		highestClusterIndex++;
		globalCluster[0][0] = highestClusterIndex;
		//this->clusters[highestClusterIndex].add(Pixel(0,0,BLACK));
		tempCluster[highestClusterIndex].add(Pixel(0, 0, BLACK));
	}
	else
		globalCluster[0][0] = 0;

	//Checking the first row
	for (int col = 1; col < this->colNum; col++)
	{
		if (this->data[0][col] == BLACK)
		{
			if (this->data[0][col-1] == BLACK) //existing cluster
				clusterIndex = globalCluster[0][col-1];
			else //new cluster
			{
				highestClusterIndex++;
				clusterIndex = highestClusterIndex;
				//this->clusters[clusterIndex]
			}
			globalCluster[0][col] = clusterIndex;
			//this->clusters[clusterIndex].add(PIxel(0, col, BLACK));
			tempCluster[clusterIndex].add(Pixel(0, col, BLACK));
		}
		else
			globalCluster[0][col] = WHITE;
	}

	//checking the rest of the image
	for (int row = 1; row != this->rowNum; row++)
	{
		int col = 0;
		if (this->data[row][col] == BLACK)
			if (this->data[row-1][col] == 0)						//existing cluster
				clusterIndex = this->globalCluster[row-1][col];
			else if (this->data[row-1][col+1] == BLACK)				//existing cluster
				clusterIndex = this->globalCluster[row-1][col+1];
			else													//new cluster
			{
				highestClusterIndex++;
				clusterIndex = highestClusterIndex;
				//this->clusters[clusterIndex].add(Pixel(row, col, BLACK));
				tempCluster[clusterIndex].add(Pixel(row, col, BLACK));
			}
		else
			this->globalCluster[row][col] = 0;


		for (col = 1; col != this->colNum-1; col++)
		{
			if (this->data[row][col] == BLACK)
			{
				if (this->data[row][col-1] == WHITE && this->data[row-1][col-1] == WHITE
						&& this->data[row-1][col] == WHITE && this->data[row-1][col+1] == WHITE) //new cluster
				{
					highestClusterIndex++;
					clusterIndex = highestClusterIndex;
				}
				else if (this->data[row][col-1] == BLACK) //existing cluster
				{
					clusterIndex = this->globalCluster[row][col-1];
					if (this->data[row-1][col+1] == BLACK && this->globalCluster[row-1][col+1] != clusterIndex) //the other cluster should be merged into the current cluster
					{
						indexOfClusterToBeMerged = this->globalCluster[row-1][col+1];
						//for (int i = 0; i != this->clusters[indexOfClusterToBeMerged].getSize(); i++) //changing the global cluster for all the pixels in the wrong cluster
						for (int i = 0; i != tempCluster[indexOfClusterToBeMerged].getSize(); i++) //changing the global cluster for all the pixels in the wrong cluster
						{
							//pixel2BChanged = this->clusters[indexOfClusterToBeMerged].element(i);
							pixel2BChanged = tempCluster[indexOfClusterToBeMerged].element(i);
							row2BChanged = pixel2BChanged.getRow();
							col2BChanged = pixel2BChanged.getCol();
							this->addPixelToGlobalCluster(pixel2BChanged, clusterIndex);
							//this->globalCluster[row2BChanged][col2BChanged] = clusterIndex;
							//this->clusters[clusterIndex].add(pixel2BChanged);
							tempCluster[clusterIndex].add(pixel2BChanged);
						}
						//this->clusters[indexOfClusterToBeMerged].clear();
						tempCluster[indexOfClusterToBeMerged].clear();
					}
				}
				else if (this->data[row-1][col-1] == BLACK) //existing cluster
				{
					clusterIndex = this->globalCluster[row-1][col-1];
					if (this->data[row-1][col-1] == BLACK && this->globalCluster[row-1][col+1] != clusterIndex)//the other cluster should be merged into the current cluster
					{
						indexOfClusterToBeMerged = this->globalCluster[row-1][col+1];
						for (int i = 0; i != tempCluster[indexOfClusterToBeMerged].getSize(); i++) //changing the global cluster for all the pixels in the wrong cluster
						{
							pixel2BChanged = tempCluster[indexOfClusterToBeMerged].element(i);
							row2BChanged = pixel2BChanged.getRow();
							col2BChanged = pixel2BChanged.getCol();
							this->addPixelToGlobalCluster(pixel2BChanged, clusterIndex);
							//this->globalCluster[row2BChanged][col2BChanged] = clusterIndex;
							tempCluster[clusterIndex].add(pixel2BChanged);
						}
						tempCluster[indexOfClusterToBeMerged].clear();
					}
				}

				else if (this->data[row-1][col] == BLACK) //existing cluster, but nothing to be merged
					clusterIndex = this->globalCluster[row-1][col];
				else
					clusterIndex = globalCluster[row-1][col+1];

				globalCluster[row][col] = clusterIndex;
				tempCluster[clusterIndex].add(Pixel(row, col, BLACK));
			} // end if (this->data[row][col] == BLACK)
			else
				globalCluster[row][col] = 0;
		}
		//now the last col
		col = this->colNum - 1;

		if (this->data[row][col] == BLACK)
		{
			if (this->data[row][col-1] == WHITE && this->data[row-1][col-1] == WHITE && this->data[row-1][col] == WHITE) //this is a new cluster
			{
				highestClusterIndex++;
				clusterIndex = highestClusterIndex;
			}
			else if (this->data[row][col-1] == BLACK) //existing cluster to the left
				clusterIndex = this->globalCluster[row][col-1];
			else if (this->data[row-1][col-1] == BLACK) // existing cluster to the up left
				clusterIndex = this->globalCluster[row-1][col-1];
			else //existing cluster to above
				clusterIndex = globalCluster[row-1][col];
			this->globalCluster[row][col] = clusterIndex;
			tempCluster[clusterIndex].add(Pixel(row, col, BLACK));
		}
		else //pixel has been white, its cluster number is 0.
			this->globalCluster[row][col] = 0;
	}
	// now post processing
	for (int clusterCount = 0; clusterCount != (int)tempCluster.size(); clusterCount++)
		if (tempCluster[clusterCount].getSize() != 0)
			this->clusters.push_back(tempCluster[clusterCount]);
}

void ImageMatrix::printData(void) const
{
	int answer = 0;
	if (this->colNum > 30 || this->rowNum > 30)
	{
		cout << "Warning! The size of the 2D array ImageMatrix::data is too large. Do you want to print it to the screen?" << endl
				<< "Enter 'n' to cancel, any other character to print: " << endl;
		cin >> answer;
		if (answer == 'n')
			return;
	}
	for (int row = 0; row != this->rowNum; row++)
	{
		cout << endl;
		for (int col = 0; col != this->colNum; col++)
			cout << this->data[row][col];// << "\t";
	}
	cout << endl;
	cout.flush();
}

void ImageMatrix::printGlobalCluster(void) const
{
	int answer = 0;
	if (this->colNum > 30 || this->rowNum > 30)
	{
		cout << "Warning! The size of the 2D array ImageMatrix::data is too large. Do you want to print it to the screen?" << endl
				<< "Enter 'n' to cancel, any other character to print: " << endl;
		cin >> answer;
		if (answer == 'n')
			return;
	}
	for (int row = 0; row != this->rowNum; row++)
	{
		cout << endl;
		for (int col = 0; col != this->colNum; col++)
			cout << this->globalCluster[row][col] << "\t";
	}
}


ImageMatrix::~ImageMatrix()
{
//	for (int i = 0; i != this->rowNum; i++)
//		delete[] this->data[i];
//	delete[] this->data;

	this->rowNum = 0;
	this->colNum = 0;
	//this->clusterNum = 0;
	this->clusterability = 0;
	//this->data = NULL;
	this->clusters.clear();
}

ImageMatrix& ImageMatrix::operator= (const ImageMatrix& other)
{
	if (this == &other)
		return *this;
	this->clusterability = other.clusterability;
	this->clusters.clear();
	//for (int i = 0; i < other.getClusterNum(); i++)
	//	this->clusters.push_back(other.clusters[i]);
	//vector<Cluster>::iterator i;
	for (vector<Cluster>::const_iterator i = other.clusters.begin(); i!= other.clusters.end(); i++)
		this->clusters.push_back(*i);
	this->clusters = other.clusters;
	this->colNum = other.colNum;
	this->rowNum = other.rowNum;
	assert (other.rowNum == MROWS && other.colNum == MCOLS && "Error: copying from an ImageMatrix of a different size");
	for (int r = 0; r < other.rowNum; r++)
		for (int c = 0; c < other.colNum; c++)
		{
			this->data[r][c] = other.data[r][c];
			this->globalCluster[r][c] = other.globalCluster[r][c];
		}
	return *this;
}


int ImageMatrix::countSimilarity (const ImageMatrix& other) const
{
	assert (colNum == other.colNum && rowNum == other.rowNum && "Cannot compare two IMs of different size");
	int result = 0;
	for (int row = 0; row < rowNum; ++row)
		for (int col = 0; col < colNum; ++col)
			if (other.data[row][col] == data[row][col])
				result++;
	return result;
}

int ImageMatrix::countDifferences (const ImageMatrix& other) const
{
	assert (colNum == other.colNum && rowNum == other.rowNum && "Cannot compare two IMs of different size");
	int result = 0;
	for (int row = 0; row < rowNum; ++row)
		for (int col = 0; col < colNum; ++col)
			if (other.data[row][col] != data[row][col])
				result++;
	return result;
}

ImageMatrix::ImageMatrix(int rowNum, int colNum): rowNum(rowNum), colNum(colNum)
{
	assert(rowNum == MROWS && colNum == MCOLS && "The input size does not match the constant values");
	for (int row = 0; row < rowNum; ++row)
		for (int col = 0; col < colNum; ++col)
			data[row][col] = -1;
}

//Neighborhood ImageMatrix::getNeighborhood(int r, int col) const
//{
//	Neighborhood nbr;
//
//}

std::ostream& operator<< (std::ostream& os, const ImageMatrix& im)
{
	for (int r = 0; r < im.getRowNum(); ++r)
	{
		for (int c = 0; c < im.getColNum(); ++c)
			os << im.getValue(r,c) << " ";
		os << endl;
	}
	return os;
}